package io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 非阻塞IO
 * 和传统BIO 相比，NIO可以处理使用一个线程处理多个客户端连接，其原理是使用多路复用器对所有注册到多路复用器上的通道进行轮询
 * 如果有io操作（连接，读，写）就执行对应的操作，如果没有就不执行操作
 * selector:多路复用器
 * buffer：缓冲区 NIO 的数据读写都是先读写到缓冲区
 * channel：和stream 不同，stream 是单向的，但是channel 是双向的既可以读又可以写
 */
public class NioServer {
    public static void main(String[] args) {
        // 创建一个server channel
        try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",9091));
            // 将server channel设置为非阻塞的
            serverSocketChannel.configureBlocking(false);
            System.out.println("server started, listening on :" + serverSocketChannel.getLocalAddress());
            // 开启一个多路复用器
            Selector selector = Selector.open();
            // 将通道注册到selector 上，并注册他所感兴趣的时间
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while(true){
                // 轮询
                selector.select();
                // 获取有io 操作的所有channel的key 集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                // 遍历处理io 操作
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    // 处理完这个key 对应的channel上的io 操作后移除这个key 避免重复操作
                    iterator.remove();
                    handle(key);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理io 操作
     * @param key
     */
    private static void handle(SelectionKey key){
        // 处理连接操作
        if (key.isAcceptable()){
            // 获取server channel
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            try {
                // 接受客户端连接
                SocketChannel sc = ssc.accept();
                // 设置客户端channel 为非阻塞
                sc.configureBlocking(false);
                // 将客户端channel 也注册到selector上，并将其感兴趣的时间设置为读事件
                sc.register(key.selector(),SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else if(key.isAcceptable()){
            // 处理读事件
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.clear();
            try(SocketChannel sc = (SocketChannel) key.channel()) {
                int len = sc.read(buffer);
                if (len!=-1){
                    System.out.println(new String(buffer.array(),0,len));
                }
                ByteBuffer bufferToWrite = ByteBuffer.wrap("hello nio client".getBytes());
                sc.write(bufferToWrite);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
